//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<CDevice9.cpp>
///	@path	~/src/graphics/dx9/
///	@date	2007/09/21
///	@desc	DirectX 9 device implementation of IDevice.

#include "config/config.h"

#include "lib/utilities/observer.h"
#include "lib/system/thread_debug.h"

#ifdef _DEBUG
#	define D3D_DEBUG_INFO 1
#endif
#include "graphics/DxHelpers.h"
#include "graphics/IDeviceCallback.h"
#include "graphics/dx9/CDevice9.h"
#include "graphics/dx9/enumeration9.h"

#include <dxerr.h>
#include <MMSystem.h>

// force linker to link these libs
#pragma comment( linker , "/defaultlib:winmm.lib" )

TRACE_CATEGORY( _S("Graphics/DX9/device") , TRACE_LV_TRACE );

namespace xeres {

	// minimum window size
	static const int DX9_MINWINDOW_SIZE_X = 200;
	static const int DX9_MINWINDOW_SIZE_Y = 200;

	IMPL_IOBJECT_CLASS_DYNAMIC( CDevice9 );

	// determine if device settings is windowed
	inline bool CDevice9::is_windowed( CDevice9::_DeviceSettings * deviceSettings )
	{
		if( deviceSettings == NULL )
			return true;
		return deviceSettings->presentParameters.Windowed == 1;
	}

	// ctor
	CDevice9::CDevice9( void )
		: m_hWnd( NULL )
		, m_hMenu( NULL )
		, m_hAdapterMonitor( NULL )
		, m_d3dDevice( NULL )
		// states
		, m_fullScreenBackBufferWidthAtModeChange( 0 )
		, m_fullScreenBackBufferHeightAtModeChange( 0 )
		, m_windowBackBufferWidthAtModeChange( 0 )
		, m_windowBackBufferHeightAtModeChange( 0 )
		, m_windowedStyleAtModeChange( 0 )
		, m_settingsValid( false )
		, m_ignoreSizeChange( false )
		, m_doNotStoreBufferSize( false )
		, m_noUpdate( true )
		, m_deviceCreated( false )
		, m_minimized( false )
		, m_deviceLost( false )
		, m_insideDeviceCallback( false )
		, m_enablePerf( false )
	{
		memset( &m_currentSettings , 0 , sizeof(_DeviceSettings) );
		memset( &m_backBufferSurfaceDesc , 0 , sizeof(D3DSURFACE_DESC) );
		memset( &m_caps , 0 , sizeof(D3DCAPS9) );
		memset( &m_windowedPlacement , 0 , sizeof(WINDOWPLACEMENT) );
	}

	// dtor
	CDevice9::~CDevice9( void )
	{
		if( m_d3dDevice )
		{
			CleanupEnvironment( true );
		}
	}

	namespace
	{
		// helper class to hold direct3d interface
		class _IDirect3D9Accessor
		{
		public:

			typedef IDirect3D9 * (WINAPI *Direct3DCreate9Fn)( UINT );

			//@ ctor
			_IDirect3D9Accessor( void )
				: m_d3d9( NULL )
				, m_usePerfHud( false )
			{
				TRACK_FUNCTION_SCOPE();

				m_d3d9 = dx9::Direct3DCreate9( D3D_SDK_VERSION );
				if( m_d3d9 == NULL )
				{
					TRACE_ERROR( _S("Failed to create Direct3D9 Interface.") );
					FATAL_ABORT( _S("Failed to create Direct3D9 Interface.") );
				}
				TRACE_INFO( _S("Direct3D9 initialized.") );
			}
			//@ dtor
			~_IDirect3D9Accessor( void )
			{
#ifndef _PYXERES
				// Some situation in python, calls below will cause crash.
 				ULONG rest = m_d3d9->Release();
				if( rest != 0 && m_usePerfHud )
				{
					TRACE_ERROR( _S("Invalid Direct3D9 interface reference count.") );
					FATAL_ABORT( _S("Invalid Direct3D9 interface reference count.") );
				}
#endif
			}
			// get operator
			IDirect3D9 * operator * ( void ) const
			{
				return m_d3d9;
			}
			// instance
			static _IDirect3D9Accessor& instance( void )
			{
				static ALIGNED _IDirect3D9Accessor s_IDirect3D9Accessor;
				return s_IDirect3D9Accessor;
			}
			// set perf hud
			void set_perf_hud( bool used )
			{
				m_usePerfHud = used;
			}

		private:
		//@ data
			IDirect3D9 * m_d3d9;

			// states
			bool m_usePerfHud;
		};
	}

	// get singleton d3d9
	IDirect3D9 * CDevice9::d3d9( void )
	{
		return *_IDirect3D9Accessor::instance();
	}

	// enable render
	bool CDevice9::IsEnableRender( void )
	{
		RUNNING_ON_THREAD( _S("main|render|!_tick") );

		if( m_d3dDevice == NULL )
			return false;

		if( m_noUpdate || !m_deviceCreated || m_minimized )
			return false;

		HRESULT hr = m_d3dDevice->TestCooperativeLevel();
		if( FAILED( hr ) )
		{
			if( hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET )
			{
				m_deviceLost = true;
				return true;
			}
			else
			{
				TRACE_ERROR( _S("CDevice9::IsEnableRender: Device is not ready to render: %s.") ,
					d3d_get_err( hr ) );
				return false;
			}
		}
		return true;
	}

	// begin render
	bool CDevice9::BeginRender( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("render|!_tick") );

		COUNTER_GUARD( _S("overhead") );

		if( m_d3dDevice == NULL )
			return false;

		if( m_deviceLost )
		{
			// Window is minimized or paused so yield CPU time to other processes
			::Sleep( 50 ); 
		}

		// test to go
		HRESULT hr = m_d3dDevice->TestCooperativeLevel();
		if( FAILED( hr ) )
		{
			if( hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET )
			{
				m_deviceLost = true;
			}
			else
			{
				TRACE_ERROR( _S("CDevice9::BeginRender: Device is not ready to render: %s.") ,
					d3d_get_err( hr ) );
				return false;
			}
		}

		// Test if device lost, need change mode.
		if( m_d3dDevice == NULL )
		{
			if( m_deviceLost )
			{
				_DeviceSettings settings = m_currentSettings;
				ChangeMode( &settings );
				return false;
			}
			return false;
		}

		// Change mode while device lost
		if( m_deviceLost )
		{
			hr = S_OK;
			 // Test the cooperative level to see if it's OK to render.
			if( FAILED( hr = m_d3dDevice->TestCooperativeLevel() ) )
			{
				if( D3DERR_DEVICELOST == hr )
				{
					// The device has been lost but cannot be reset at this time.
					// So wait until it can be reset.
					return false;
				}

				// If we are windowed, read the desktop format and 
				// ensure that the Direct3D device is using the same format 
				// since the user could have changed the desktop bitdepth 
				if( m_currentSettings.presentParameters.Windowed )
				{
					D3DDISPLAYMODE adapterDesktopDisplayMode;
					_DeviceSettings * pDeviceSettings = &m_currentSettings;
					d3d9()->GetAdapterDisplayMode( pDeviceSettings->adapterOrdinal, &adapterDesktopDisplayMode );
					if( pDeviceSettings->adapterFormat != adapterDesktopDisplayMode.Format )
					{
						_MatchOptions options;
						options.AdapterOrdinal     = MATCH_PRESERVE_INPUT;
						options.DeviceType         = MATCH_PRESERVE_INPUT;
						options.Windowed           = MATCH_PRESERVE_INPUT;
						options.AdapterFormat      = MATCH_PRESERVE_INPUT;
						options.VertexProcessing   = MATCH_CLOSEST_TO_INPUT;
						options.Resolution         = MATCH_CLOSEST_TO_INPUT;
						options.BackBufferFormat   = MATCH_CLOSEST_TO_INPUT;
						options.BackBufferCount    = MATCH_CLOSEST_TO_INPUT;
						options.MultiSample        = MATCH_CLOSEST_TO_INPUT;
						options.SwapEffect         = MATCH_CLOSEST_TO_INPUT;
						options.DepthFormat        = MATCH_CLOSEST_TO_INPUT;
						options.StencilFormat      = MATCH_CLOSEST_TO_INPUT;
						options.PresentFlags       = MATCH_CLOSEST_TO_INPUT;
						options.RefreshRate        = MATCH_CLOSEST_TO_INPUT;
						options.PresentInterval    = MATCH_CLOSEST_TO_INPUT;

						_DeviceSettings deviceSettings = m_currentSettings;
						deviceSettings.adapterFormat = adapterDesktopDisplayMode.Format;
						_DeviceSettings optimal;
						BuildOptimalSettings( &deviceSettings , &optimal , &options );
						// the call will fail if no valid devices were found
						if( FindValidSettings( &deviceSettings, &deviceSettings, &optimal , &options ) )
						{
							TRACE_ERROR( _S("CDevice9::BeginRender: Device is compatible with settings.") );
							FATAL_ABORT( _S("Device is not compatible.") );
						}

						// Change to a Direct3D device created from the new device settings.
						// If there is an existing device, then either reset or recreate the scene
						hr = ChangeMode( &deviceSettings );
						if( FAILED(hr) )  
						{
							TRACE_ERROR( _S("CDevice9::BeginRender: Failed to recreate device while device lost: %s.") ,
								d3d_get_err( hr ) );
							FATAL_ABORT( _S("Failed to change mode while device lost.") );
						}
						return true;
					}
				}

				// Try to reset the device
				if( FAILED( hr = ResetEnvironment() ) )
				{
					if( D3DERR_DEVICELOST == hr )
					{
						// The device was lost again, so continue waiting until it can be reset.
						return false;
					}
					else
					{
						// Reset failed, but the device wasn't lost so something bad happened, 
						// so recreate the device to try to recover
						_DeviceSettings settings = m_currentSettings;
						if( FAILED( ChangeMode( &settings ) ) )
						{
							TRACE_ERROR( _S("CDevice9::BeginRender: Failed to recreate device while device lost: %s.") ,
								d3d_get_err( hr ) );
							FATAL_ABORT( _S("Failed to change mode while device lost.") );
						}
					}
				}
			}

			m_deviceLost = false;
		}
		if( m_deviceLost )
		{
			// Try to reset the device
			if( FAILED( hr = ResetEnvironment() ) )
			{
				if( D3DERR_DEVICELOST == hr )
				{
					// The device was lost again, so continue waiting until it can be reset.
					return false;
				}
				else
				{
					// Reset failed, but the device wasn't lost so something bad happened, 
					// so recreate the device to try to recover
					_DeviceSettings settings = m_currentSettings;
					if( FAILED( ChangeMode( &settings ) ) )
					{
						TRACE_ERROR( _S("CDevice9::BeginRender: Failed to recreate device while device lost: %s.") ,
							d3d_get_err( hr ) );
						FATAL_ABORT( _S("Failed to change mode while device lost.") );
					}
				}
			}
		}

		return true;
	}

	// end render
	void CDevice9::EndRender( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("render|!_tick") );

		COUNTER_GUARD( _S("overhead") );

		assert( !m_noUpdate && !m_deviceLost && m_deviceCreated && !m_minimized );

		// present update
		HRESULT hr = m_d3dDevice->Present( NULL , NULL , NULL , NULL );
		if( FAILED(hr) )
		{
			TRACE_ERROR( _S("CDevice9::EndRender: Failed to present: %s.") , d3d_get_err(hr) );
		}
	}

	// ToggleFullScreen
	bool CDevice9::ToggleFullScreen( void * window )
	{
		// must run on device
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("device") );

		// Get the current device settings and flip the windowed state then
		// find the closest valid device settings with this change
		_DeviceSettings deviceSettings = m_currentSettings;
		_DeviceSettings orginalDeviceSettings = m_currentSettings;

		// Toggle windowed/fullscreen bit
		deviceSettings.presentParameters.Windowed = !deviceSettings.presentParameters.Windowed;

		_MatchOptions options;
		options.AdapterOrdinal     = MATCH_PRESERVE_INPUT;
		options.DeviceType         = MATCH_CLOSEST_TO_INPUT;
		options.Windowed           = MATCH_PRESERVE_INPUT;
		options.AdapterFormat      = MATCH_IGNORE_INPUT;
		options.VertexProcessing   = MATCH_CLOSEST_TO_INPUT;
		options.BackBufferFormat   = MATCH_IGNORE_INPUT;
		options.BackBufferCount    = MATCH_CLOSEST_TO_INPUT;
		options.MultiSample        = MATCH_CLOSEST_TO_INPUT;
		options.SwapEffect         = MATCH_CLOSEST_TO_INPUT;
		options.DepthFormat        = MATCH_CLOSEST_TO_INPUT;
		options.StencilFormat      = MATCH_CLOSEST_TO_INPUT;
		options.PresentFlags       = MATCH_CLOSEST_TO_INPUT;
		options.RefreshRate        = MATCH_IGNORE_INPUT;
		options.PresentInterval    = MATCH_CLOSEST_TO_INPUT;

		// Go back to previous state

		BOOL bIsWindowed = is_windowed( &deviceSettings );
		UINT nWidth  = ( bIsWindowed ) ?
			m_windowBackBufferWidthAtModeChange : m_fullScreenBackBufferWidthAtModeChange;
		UINT nHeight = ( bIsWindowed ) ?
			m_windowBackBufferHeightAtModeChange : m_fullScreenBackBufferHeightAtModeChange;

		if( nWidth > 0 && nHeight > 0 )
		{
			options.Resolution = MATCH_CLOSEST_TO_INPUT;

			deviceSettings.presentParameters.BackBufferWidth = nWidth;
			deviceSettings.presentParameters.BackBufferHeight = nHeight;
		}
		else
		{
			// No previous data, so just switch to defaults
			options.Resolution = MATCH_IGNORE_INPUT;
		}

		_DeviceSettings optimal;
		BuildOptimalSettings( &deviceSettings , &optimal , &options );
		if( FindValidSettings( &deviceSettings , &deviceSettings , &optimal , &options ) ) 
		{
			// Create a Direct3D device using the new device settings.  
			// If there is an existing device, then it will either reset or recreate the scene.
			HRESULT hr = ChangeMode( &deviceSettings );

			// If hr == E_ABORT, this means the app rejected the device settings in the ModifySettingsCallback so nothing changed
			if( FAILED(hr) && (hr != E_ABORT) )
			{
				// Failed creating device, try to switch back.
				HRESULT hr2 = ChangeMode( &orginalDeviceSettings );
				if( FAILED(hr2) )
				{
					// If this failed, then shutdown
					TRACE_ERROR( _S("CDevice9::ToggleFullScreen: Failed to toggle full screen: %s.") ,
						d3d_get_err( hr2 ) );
					FATAL_ABORT( _S("Failed to toggle full screen.") );
				}
			}
		}
		return true;
	}

	// IsFullScreen
	bool CDevice9::IsFullScreen( void )
	{
		return !is_windowed( &m_currentSettings );
	}

	// attach device callback
	void CDevice9::AttachCallback( RefWeak<IDeviceCallback> callback )
	{
		// all callback changes should be synchronized
		RUNNING_ON_THREAD( _S("main") );

		// lock operation
		LockGuard<ThreadLock> __lockGuard( m_lock );
		m_callbacks.push_back( callback );
	}

	// determine if device can be reset
	bool CDevice9::CanDeviceBeReset( _DeviceSettings * oldSettings , _DeviceSettings * newSettings )
	{
		if( oldSettings == NULL )
			return false;
		return 
			( oldSettings->adapterOrdinal == newSettings->adapterOrdinal ) &&
            ( oldSettings->deviceType == newSettings->deviceType ) &&
            ( oldSettings->behaviorFlags == newSettings->behaviorFlags );
	}

	// change device
	HRESULT CDevice9::ChangeMode( _DeviceSettings * newSettings )
	{
		TRACK_FUNCTION_SCOPE();

		HRESULT hr = S_OK;

		TRACE_INFO( _S("Change DirectX device mode.") );
		// change device

		_DeviceSettings * oldSettings = m_settingsValid ? &m_currentSettings : NULL;

		// invoke ChangeModePreEvent to all registered callbacks
		m_insideDeviceCallback = true;
		for( size_t i = 0 , total = m_callbacks.size() ; i < total ; ++i )
		{
			bool res = m_callbacks[i]->PreChangeMode( this , oldSettings , newSettings );
			// handle result
			if( !res )
			{
				FATAL_ABORT( _S("Device pre-change mode callback failed.") );
			}
		}
		m_insideDeviceCallback = false;

		// pause device loop (pause update loop)
		m_noUpdate = true;

		// omit size change events while changing mode
		m_ignoreSizeChange = true;

		// check if we need to change window size
		bool bKeepCurrentWindowSize = 
			( newSettings->presentParameters.BackBufferWidth == 0 ) &&
			( newSettings->presentParameters.BackBufferHeight == 0 );

		// -------------------------------
		// Before we reset device
		// -------------------------------

		// check windowed
		if( is_windowed( newSettings ) )
		{
			// enter windowed mode
			if( oldSettings && !is_windowed( oldSettings ) )
			{
				TRACE_INFO( _S("Enter windowed mode.") );
				// change from fullscreen to windowed
				m_fullScreenBackBufferWidthAtModeChange =
					oldSettings->presentParameters.BackBufferWidth;
				m_fullScreenBackBufferHeightAtModeChange =
					oldSettings->presentParameters.BackBufferHeight;

				// Restore windowed mode style
				:: SetWindowLong( m_hWnd , GWL_STYLE , m_windowedStyleAtModeChange );
			}

			// Reattach menu if one exists
			if( m_hMenu != NULL )
				::SetMenu( m_hWnd , m_hMenu );
		}
		else
		{
			// enter fullscreen mode
			if( oldSettings == NULL || ( oldSettings && is_windowed( oldSettings ) ) )
			{
				TRACE_INFO( _S("Enter full-screen mode.") );
				// Transitioning to full screen mode from a standard window so 
				// save current window position/size/style now in case the user toggles to windowed mode later 
				DWORD dwStyle = ::GetWindowLong( m_hWnd , GWL_STYLE );
				dwStyle &= ~WS_MAXIMIZE & ~WS_MINIMIZE; // remove minimize/maximize style
				m_windowedStyleAtModeChange = dwStyle;
				if( oldSettings )
				{
					// change from fullscreen to windowed
					m_windowBackBufferWidthAtModeChange =
						oldSettings->presentParameters.BackBufferWidth;
					m_windowBackBufferHeightAtModeChange =
						oldSettings->presentParameters.BackBufferHeight;
				}
			}

			// Hide the window to avoid animation of blank windows
			::ShowWindow( m_hWnd , SW_HIDE );

			// Set FS window style
			::SetWindowLong( m_hWnd , GWL_STYLE, WS_POPUP|WS_SYSMENU );

			// save and remove menu 
			m_hMenu = ::GetMenu( m_hWnd );
			::SetMenu( m_hWnd , NULL );

			WINDOWPLACEMENT wpFullscreen;
			::ZeroMemory( &wpFullscreen, sizeof(WINDOWPLACEMENT) );
			wpFullscreen.length = sizeof(WINDOWPLACEMENT);
			::GetWindowPlacement( m_hWnd , &wpFullscreen );
			if( (wpFullscreen.flags & WPF_RESTORETOMAXIMIZED) != 0 )
			{
				// Restore the window to normal if the window was maximized then minimized.  This causes the 
				// WPF_RESTORETOMAXIMIZED flag to be set which will cause SW_RESTORE to restore the 
				// window from minimized to maximized which isn't what we want
				wpFullscreen.flags &= ~WPF_RESTORETOMAXIMIZED;
				wpFullscreen.showCmd = SW_RESTORE;
				::SetWindowPlacement( m_hWnd, &wpFullscreen );
			}
		}

		// reset device & create device
		if( CanDeviceBeReset( oldSettings , newSettings ) )
		{
			memcpy( &m_currentSettings , newSettings , sizeof(_DeviceSettings) );

			hr = ResetEnvironment();

			if( FAILED(hr) )
			{
				if( D3DERR_DEVICELOST == hr )
				{
					// The device is lost, just mark it as so and continue on with 
					// capturing the state and resizing the window/etc.
					m_deviceLost = true;
				}
			}
		}
		else
		{
			// Cleanup if not first device created
			if( oldSettings ) 
				CleanupEnvironment( false );

			// Create the D3D device and call the app's device callbacks

			memcpy( &m_currentSettings , newSettings , sizeof(_DeviceSettings) );
			hr = CreateEnvironment();

			if( FAILED(hr) )
			{
				CleanupEnvironment( true );
				TRACE_ERROR( _S("CDevice9::ChangeMode: Failed to setup environment from new settings: %s") ,
					d3d_get_err( hr ) );
				FATAL_ABORT( _S("Device create environment failed.") );
				return hr;
			}
		}

		// m_hAdapterMonitor = getMonitorFromAdapter();

		// FIXME: update frame statistics

		// Restore window placement
		if( oldSettings && ! is_windowed( oldSettings ) && is_windowed( newSettings ) )
		{
			// Going from fullscreen -> windowed

			// Restore the show state, and positions/size of the window to what it was
			// It is important to adjust the window size 
			// after resetting the device rather than beforehand to ensure 
			// that the monitor resolution is correct and does not limit the size of the new window.
			::SetWindowPlacement( m_hWnd , &m_windowedPlacement );

			// Also restore the z-order of window to previous state
			HWND hWndInsertAfter = /* etTopmostWhileWindowed() ? HWND_TOPMOST : */ HWND_NOTOPMOST;
			::SetWindowPos( m_hWnd , hWndInsertAfter , 0 , 0 , 0 , 0 , SWP_NOMOVE|SWP_NOREDRAW|SWP_NOSIZE );
		}

		// Check to see if the window needs to be resized.  
		// Handle cases where the window is minimized and maximized as well.
		bool bNeedToResize = false;
		if( is_windowed( newSettings ) &&	// only resize if in windowed mode
			!bKeepCurrentWindowSize )		// only resize if presentParameters.BackbufferWidth/Height were not 0
		{
			UINT nClientWidth;
			UINT nClientHeight;    
			if( ::IsIconic( m_hWnd ) )
			{
				// Window is currently minimized. To tell if it needs to resize, 
				// get the client rect of window when its restored the 
				// hard way using GetWindowPlacement()
				WINDOWPLACEMENT wp;
				memset( &wp , 0 , sizeof(WINDOWPLACEMENT) );
				wp.length = sizeof(WINDOWPLACEMENT);
				::GetWindowPlacement( m_hWnd , &wp );

				if( ( wp.flags & WPF_RESTORETOMAXIMIZED ) != 0 && wp.showCmd == SW_SHOWMINIMIZED )
				{
					// WPF_RESTORETOMAXIMIZED means that when the window is restored it will
					// be maximized.  So maximize the window temporarily to get the client rect 
					// when the window is maximized.  GetSystemMetrics( SM_CXMAXIMIZED ) will give this 
					// information if the window is on the primary but this will work on multimon.
					::ShowWindow( m_hWnd , SW_RESTORE );
					RECT rcClient;
					::GetClientRect( m_hWnd , &rcClient );
					nClientWidth  = (UINT)(rcClient.right - rcClient.left);
					nClientHeight = (UINT)(rcClient.bottom - rcClient.top);
					::ShowWindow( m_hWnd , SW_MINIMIZE );
				}
				else
				{
					// Use wp.rcNormalPosition to get the client rect, but wp.rcNormalPosition 
					// includes the window frame so subtract it
					RECT rcFrame = {0};
					::AdjustWindowRect( &rcFrame, m_windowedStyleAtModeChange , FALSE );
					LONG nFrameWidth = rcFrame.right - rcFrame.left;
					LONG nFrameHeight = rcFrame.bottom - rcFrame.top;
					nClientWidth  = (UINT)(wp.rcNormalPosition.right - wp.rcNormalPosition.left - nFrameWidth);
					nClientHeight = (UINT)(wp.rcNormalPosition.bottom - wp.rcNormalPosition.top - nFrameHeight);
				}
			}
			else
			{
				// Window is restored or maximized so just get its client rect
				RECT rcClient;
				::GetClientRect( m_hWnd, &rcClient );
				nClientWidth  = (UINT)(rcClient.right - rcClient.left);
				nClientHeight = (UINT)(rcClient.bottom - rcClient.top);
			}

			// Now that we know the client rect, compare it against the back buffer size
			// to see if the client rect is already the right size
			if( nClientWidth  != newSettings->presentParameters.BackBufferWidth ||
				nClientHeight != newSettings->presentParameters.BackBufferHeight )
			{
				bNeedToResize = true;
			}
		}

		// Only resize window if needed 
		if( bNeedToResize ) 
		{
			// Need to resize, so if window is maximized or minimized then restore the window
			if( ::IsIconic( m_hWnd ) ) 
				::ShowWindow( m_hWnd , SW_RESTORE );
			if( ::IsZoomed( m_hWnd ) ) // doing the IsIconic() check first also handles the WPF_RESTORETOMAXIMIZED case
				::ShowWindow( m_hWnd , SW_RESTORE );

			// Make a window rect with a client rect that is the same size as the backbuffer
			RECT rcWindow = {0};
			rcWindow.right = (long)( newSettings->presentParameters.BackBufferWidth );
			rcWindow.bottom = (long)( newSettings->presentParameters.BackBufferHeight );
			::AdjustWindowRect( &rcWindow , ::GetWindowLong( m_hWnd , GWL_STYLE ) , FALSE );

			// Resize the window.  It is important to adjust the window size 
			// after resetting the device rather than beforehand to ensure 
			// that the monitor resolution is correct and does not limit the size of the new window.
			int cx = (int)(rcWindow.right - rcWindow.left);
			int cy = (int)(rcWindow.bottom - rcWindow.top);
			::SetWindowPos( m_hWnd , 0 , 0 , 0 , cx , cy , SWP_NOZORDER|SWP_NOMOVE );

			// Its possible that the new window size is not what we asked for.  
			// No window can be sized larger than the desktop, so see if the Windows OS resized the 
			// window to something smaller to fit on the desktop.  Also if WM_GETMINMAXINFO
			// will put a limit on the smallest/largest window size.
			RECT rcClient;
			::GetClientRect( m_hWnd , &rcClient );
			UINT nClientWidth  = (UINT)(rcClient.right - rcClient.left);
			UINT nClientHeight = (UINT)(rcClient.bottom - rcClient.top);
			if( nClientWidth  != newSettings->presentParameters.BackBufferWidth  ||
				nClientHeight != newSettings->presentParameters.BackBufferHeight )
			{
				// If its different, then resize the backbuffer again.  This time create a backbuffer that matches the 
				// client rect of the current window w/o resizing the window.
				_DeviceSettings deviceSettings = m_currentSettings;
				deviceSettings.presentParameters.BackBufferWidth = 0; 
				deviceSettings.presentParameters.BackBufferHeight = 0;

				hr = ChangeMode( &deviceSettings );
				if( FAILED( hr ) )
				{
					CleanupEnvironment( true );
					m_ignoreSizeChange = false;
					m_noUpdate = false;
					return hr;
				}
			}
		}

		// Make the window visible
		if( !::IsWindowVisible( m_hWnd ) )
			::ShowWindow( m_hWnd , SW_SHOW );

		// Ensure that the display doesn't power down when fullscreen but does when windowed
		if( !is_windowed( newSettings ) )
			::SetThreadExecutionState( ES_DISPLAY_REQUIRED | ES_CONTINUOUS ); 
		else
			::SetThreadExecutionState( ES_CONTINUOUS );   

		// invoke ChangeModePostEvent to all registered callbacks
		m_insideDeviceCallback = true;
		for( size_t i = 0 , total = m_callbacks.size() ; i < total ; ++i )
		{
			bool res = m_callbacks[i]->PostChangeMode( this , &m_currentSettings );
			// handle result
			if( !res )
			{
				FATAL_ABORT( _S("Device post change mode callback failed.") );
			}
		}
		m_insideDeviceCallback = false;

		// restore states
		m_ignoreSizeChange = false;
		m_deviceCreated = true;
		m_settingsValid = true;
		m_noUpdate = false;

		return S_OK;
	}

	// create 3d environment
	HRESULT CDevice9::CreateEnvironment( void )
	{
		TRACK_FUNCTION_SCOPE();
		HRESULT hr = S_OK;

		// create new device object
		if( m_d3dDevice == NULL )
		{
			UINT adapter = m_currentSettings.adapterOrdinal;
			D3DDEVTYPE devtype = m_currentSettings.deviceType;

			// Set NVPref HUD available.
			if( m_enablePerf )
			{
				for( UINT i = 0 , total = d3d9()->GetAdapterCount() ; i < total ; ++i )
				{
					D3DADAPTER_IDENTIFIER9 identifier;
					HRESULT res = d3d9()->GetAdapterIdentifier( i , 0 , &identifier );
					if( strstr( identifier.Description , "PerfHUD" ) != 0 )
					{
						TRACE_INFO( _S("Found NVIDIA PerfHUD adapter.") );
						adapter = i;
						devtype = D3DDEVTYPE_REF;
						_IDirect3D9Accessor::instance().set_perf_hud( true );
						break;
					}
				}
			}
			else
			{
				_IDirect3D9Accessor::instance().set_perf_hud( false );
			}

			TRACE_INFO( _S("Creating Direct3D 9 device...") );

			// Extra: print device info.
			{
				if( devtype == D3DDEVTYPE_HAL )
					TRACE_INFO( _S("D3D Device Type HAL") );
				else if( devtype == D3DDEVTYPE_REF )
					TRACE_INFO( _S("D3D Device Type REF") );
				else
					TRACE_INFO( _S("D3D Device Type SW") );

				if( m_currentSettings.behaviorFlags & D3DCREATE_MULTITHREADED )
					TRACE_INFO( _S("D3D Device using multi-thread") );
				else
					TRACE_INFO( _S("D3D Device using single-thread") );

				if( m_currentSettings.behaviorFlags & D3DCREATE_MIXED_VERTEXPROCESSING )
					TRACE_INFO( _S("D3D Device using mixed vertex processing") );
				else if( m_currentSettings.behaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING )
					TRACE_INFO( _S("D3D Device using software vertex processing") );
				else if( m_currentSettings.behaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING )
					TRACE_INFO( _S("D3D Device using hardware vertex processing") );

				TRACE_INFO( _S("D3D with back buffer %i X %i color %i bit depth %i bit") ,
					m_currentSettings.presentParameters.BackBufferWidth ,
					m_currentSettings.presentParameters.BackBufferHeight ,
					d3d_get_color_bits( m_currentSettings.presentParameters.BackBufferFormat ) ,
					d3d_get_depth_bits( m_currentSettings.presentParameters.AutoDepthStencilFormat )
					);
			}

			// create device
			hr = d3d9()->CreateDevice( adapter , 
				devtype , m_hWnd , m_currentSettings.behaviorFlags ,
				&( m_currentSettings.presentParameters ) , &m_d3dDevice );

			if( hr == D3DERR_DEVICELOST )
			{
				m_deviceLost = true;
				return S_OK;
			}
			else if( FAILED( hr ) )
			{
				// trace error
				TRACE_ERROR( _S("CDevice9::createEnvironment: Failed to create device : %s.") ,
					d3d_get_err(hr) );
				return hr;
			}
		}

		// update back buffer desc
		UpdateBackBufferDesc();

		// update d3d caps
		m_d3dDevice->GetDeviceCaps( &m_caps );

		// Update the device stats text
		EnumD3D9 * pd3dEnum = &EnumD3D9::instance();
		EnumAdapterInfo9 * pAdapterInfo = pd3dEnum->GetAdapterInfo( m_currentSettings.adapterOrdinal );

		// call reset device callback
		m_insideDeviceCallback = true;
		for( size_t i = 0 , total = m_callbacks.size() ; i < total ; ++i )
		{	
			bool res = m_callbacks[i]->OnResetDevice( this  );
			// handle result
			if( !res )
			{
				// FIXME: trace error
				FATAL_ABORT( _S("Device reset device callback failed.") );
			}
		}
		m_insideDeviceCallback = false;

		return S_OK;
	}

	// update back buffer desc
	void CDevice9::UpdateBackBufferDesc( void )
	{
		HRESULT hr;
		IDirect3DSurface9 * pBackBuffer;
		hr = m_d3dDevice->GetBackBuffer( 0 , 0 , D3DBACKBUFFER_TYPE_MONO , &pBackBuffer );
		memset( &m_backBufferSurfaceDesc , 0 , sizeof(D3DSURFACE_DESC) );
		if( SUCCEEDED(hr) )
		{
			pBackBuffer->GetDesc( &m_backBufferSurfaceDesc );
			pBackBuffer->Release();
		}
	}

	// reset device
	HRESULT CDevice9::ResetEnvironment( void )
	{
		TRACK_FUNCTION_SCOPE();

		HRESULT hr;
		assert( m_d3dDevice );

		// Call lost device callback
		m_insideDeviceCallback = true;
		for( size_t i = 0 , total = m_callbacks.size() ; i < total ; ++i )
		{
			bool res = m_callbacks[i]->OnLostDevice( this );
			// handle result
			if( !res )
			{
				FATAL_ABORT( _S("Device lost device callback failed.") );
			}
		}
		m_insideDeviceCallback = false;

		// Reset the device
		hr = m_d3dDevice->Reset( &( m_currentSettings.presentParameters ) );
		if( FAILED(hr) )
		{
			// Reset could legitimately fail if the device is lost
			if( hr == D3DERR_DEVICELOST )
				return D3DERR_DEVICELOST;

			// FIXME: trace error
			FATAL_ABORT( _S("Device reset device failed.") );
		}

		// Update back buffer description
		UpdateBackBufferDesc();

		// FIXME: should set cursor here

		// Call reset device callback
		m_insideDeviceCallback = true;
		for( size_t i = 0 , total = m_callbacks.size() ; i < total ; ++i )
		{
			bool res = m_callbacks[i]->OnResetDevice( this );
			// handle result
			if( !res )
			{
				FATAL_ABORT( _S("Device reset device callback failed.") );
			}
		}
		m_insideDeviceCallback = false;

		return S_OK;
	}

	// cleanup environment
	void CDevice9::CleanupEnvironment( bool resetSettings )
	{
		if( m_d3dDevice == NULL )
			return;

		m_insideDeviceCallback = true;
		for( size_t i = 0 , total = m_callbacks.size() ; i < total ; ++i )
		{
			// Call the app's device lost callback
			bool res = m_callbacks[i]->OnLostDevice( this );
			// handle result
			if( !res )
			{
				FATAL_ABORT( _S("Device lost device callback failed.") );
			}
			// Call the app's device destroyed callback
			res = m_callbacks[i]->OnDestroyDevice( this );
			// handle result
			if( !res )
			{
				FATAL_ABORT( _S("Device destroy device callback failed.") );
			}
		}
		m_insideDeviceCallback = false;

		if( m_d3dDevice )
		{
			UINT references = m_d3dDevice->Release();
            if( references > 0 )
            {
				TRACE_ERROR( _S("Invalid Direct3D 9 Device Interface reference counter.") );
				FATAL_ABORT( _S("Invalid Direct3D 9 Device Interface reference counter.") );
            }
		}
		m_d3dDevice = NULL;

		if( resetSettings )
        {
            memset( &m_currentSettings , 0 , sizeof(_DeviceSettings) );
			m_settingsValid = false;
        }
		memset( &m_backBufferSurfaceDesc , 0 , sizeof(D3DSURFACE_DESC) );
		memset( &m_caps , 0 , sizeof(D3DCAPS9) );
		m_deviceCreated = false;
	}

	// tick
	void CDevice9::Tick( const Frame& frame )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("tick") );

		if( m_d3dDevice == NULL )
			return;

		m_insideDeviceCallback = true;
		for( size_t i = 0 , total = m_callbacks.size() ; i < total ; ++i )
		{
			// Call the app's tick callback
			m_callbacks[i]->Tick( frame );
		}
		m_insideDeviceCallback = false;
	}

	// check for size change
	void CDevice9::CheckForSizeChange( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("device") );

		RECT rcCurrentClient;
		::GetClientRect( m_hWnd , &rcCurrentClient );
        if( (UINT)rcCurrentClient.right != m_currentSettings.presentParameters.BackBufferWidth ||
            (UINT)rcCurrentClient.bottom != m_currentSettings.presentParameters.BackBufferHeight  )
        {
            // A new window size will require a new backbuffer size size
			_DeviceSettings newSettings;
			memcpy( &newSettings , &m_currentSettings , sizeof(_DeviceSettings) );
			newSettings.presentParameters.BackBufferWidth = 0;
            newSettings.presentParameters.BackBufferHeight = 0;
            ChangeMode( &newSettings );
        }
	}

	// EnterSizing
	void CDevice9::EnterSizing( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("device") );

		m_ignoreSizeChange = true;
	}

	// ExitSizing
	void CDevice9::ExitSizing( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("device") );

		m_ignoreSizeChange = false;

		if( !m_minimized )
			CheckForSizeChange();
	}

	// on size
	void CDevice9::OnSize( bool minized )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("device") );

		if( m_ignoreSizeChange || !m_deviceCreated || !is_windowed( &m_currentSettings ) )
			return;

		// lock operation
		LockGuard<ThreadLock> __lockGuard( m_lock );

		if( !minized )
		{
			CheckForSizeChange();
		}
		m_minimized = minized;
	}

	// GetResolution
	void CDevice9::GetResolution( int& xres , int& yres )
	{
		xres = (int)m_currentSettings.presentParameters.BackBufferWidth;
		yres = (int)m_currentSettings.presentParameters.BackBufferHeight;
	}

	// Initialize
	bool CDevice9::Initialize( void * window , RefWeak<IData> config )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("device") );

		HRESULT hr = S_OK;

		// override NV Perf HUD feature
		if( config.IsValid() )
			m_enablePerf = config->ReadBool( _S("device/perf") , false );

		EnumD3D9::instance().Initialize( m_enablePerf );

		// lock operation
		LockGuard<ThreadLock> __lockGuard( m_lock );
		
		// check if initialized
		if( m_d3dDevice != NULL )
		{
			TRACE_ERROR( _S("CDevice9::initialize: Device initialized.") );
			return false;
		}

		// start to perform initialization
		TRACE_INFO( _S("Initializing DirectX 9 device...") );		

		m_hWnd = (HWND)window;

		// FIXME: we ignore config currently, just perform default initialization

		_DeviceSettings settings;
		memset( &settings , 0 , sizeof(_DeviceSettings) );

		DWORD multithread		= D3DCREATE_MULTITHREADED;
		DWORD vertexprocessing	= D3DCREATE_MIXED_VERTEXPROCESSING;
		DWORD enablestate		= 0;

		// back buffer parameters
		settings.adapterFormat = D3DFMT_X8R8G8B8;
		settings.presentParameters.BackBufferFormat = D3DFMT_X8R8G8B8;
		settings.presentParameters.BackBufferWidth = 800;
		settings.presentParameters.BackBufferHeight = 600;
		settings.presentParameters.BackBufferCount = 1;
		settings.presentParameters.MultiSampleType = D3DMULTISAMPLE_NONE;
		settings.presentParameters.MultiSampleQuality = 0;
		settings.presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
		settings.presentParameters.Windowed = TRUE;
		settings.presentParameters.hDeviceWindow = m_hWnd;
		settings.presentParameters.EnableAutoDepthStencil = TRUE;
		settings.presentParameters.AutoDepthStencilFormat = D3DFMT_D24X8;
		settings.presentParameters.Flags =
			D3DPRESENTFLAG_DEVICECLIP |
			D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
		settings.presentParameters.FullScreen_RefreshRateInHz = 0;
		settings.presentParameters.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;

		// get config from settings
		if( config.IsValid() )
		{
			settings.presentParameters.BackBufferWidth	= config->ReadInt( _S("window/width") , 800 );
			settings.presentParameters.BackBufferHeight	= config->ReadInt( _S("window/height") , 600 );
			settings.presentParameters.Windowed			= config->ReadBool( _S("window/fullscreen") , false ) ? FALSE : TRUE ;
			settings.presentParameters.BackBufferCount	= config->ReadBool( _S("device/triple_buffer") , false ) ? 2 : 1;

			enablestate = config->ReadBool( _S("device/statistic") , false ) ? /*D3DCREATE_ENABLE_PRESENTSTATS*/ 0 : 0;
			multithread = config->ReadBool( _S("device/multi_thread") , true ) ? D3DCREATE_MULTITHREADED : 0;
			WString mode = config->ReadString( _S("device/vertex_processing") , _S("mixed") );
			if( mode == _S("mixed") )
			{
				vertexprocessing = D3DCREATE_MIXED_VERTEXPROCESSING;
				TRACE_INFO( _S("Use mixed vertex process...") );
			}
			else if( mode == _S("hardware") )
			{
				vertexprocessing = D3DCREATE_HARDWARE_VERTEXPROCESSING;
				TRACE_INFO( _S("Use hardware vertex process...") );
			}
			else if( mode == _S("software") )
			{
				vertexprocessing = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
				TRACE_INFO( _S("Use software vertex process...") );
			}
			else
			{
				TRACE_ERROR( _S("CDevice9::Initialize: Unrecognized vertex processing mode '%s'.") , mode.c_str() );
				vertexprocessing = D3DCREATE_MIXED_VERTEXPROCESSING;
			}

			if( config->ReadBool( _S("device/present_limit") , true ) )
			{
				settings.presentParameters.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
			}
			else
			{
				settings.presentParameters.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
			}
		}

		settings.adapterOrdinal = D3DADAPTER_DEFAULT;
		settings.deviceType = D3DDEVTYPE_HAL;
		settings.behaviorFlags = multithread | vertexprocessing | enablestate;

		// select proper device
		_MatchOptions options;
        options.AdapterOrdinal     = MATCH_CLOSEST_TO_INPUT;
        options.DeviceType         = MATCH_CLOSEST_TO_INPUT;
        options.Windowed           = MATCH_CLOSEST_TO_INPUT;
        options.AdapterFormat      = MATCH_CLOSEST_TO_INPUT;
        options.VertexProcessing   = MATCH_CLOSEST_TO_INPUT;
        options.Resolution         = MATCH_CLOSEST_TO_INPUT;
        options.BackBufferFormat   = MATCH_CLOSEST_TO_INPUT;
        options.BackBufferCount    = MATCH_CLOSEST_TO_INPUT;
        options.MultiSample        = MATCH_CLOSEST_TO_INPUT;
        options.SwapEffect         = MATCH_CLOSEST_TO_INPUT;
        options.DepthFormat        = MATCH_CLOSEST_TO_INPUT;
        options.StencilFormat      = MATCH_CLOSEST_TO_INPUT;
        options.PresentFlags       = MATCH_CLOSEST_TO_INPUT;
        options.RefreshRate        = MATCH_CLOSEST_TO_INPUT;
        options.PresentInterval    = MATCH_CLOSEST_TO_INPUT;

		_DeviceSettings final , optimal;
		BuildOptimalSettings( &settings , &optimal , &options );
		if( !FindValidSettings( &settings , &final , &optimal , &options ) )
		{
			TRACE_ERROR( _S("CDevice9::Initialize: Failed to find valid settings for Direct3D.") );
			FATAL_ABORT( _S("No valid device settings for requirement.") );
		}

		TRACE_INFO( _S("Found valid settings %i X %i %s %s") ,
			final.presentParameters.BackBufferWidth , final.presentParameters.BackBufferHeight ,
			d3d_format_to_string( final.adapterFormat ).c_str() ,
			d3d_devtype_to_string( final.deviceType ).c_str() );

		// change device mode
		if( FAILED( ChangeMode( &settings ) ) )
		{
			// clean up
			return false;
		}

		m_noUpdate = false;

		// set mm period
		::timeBeginPeriod(1);

		return true;
	}

	// finalize
	void CDevice9::Finalize( void )
	{
		TRACK_FUNCTION_SCOPE();
		RUNNING_ON_THREAD( _S("device") );

		// lock operation
		LockGuard<ThreadLock> __lockGuard( m_lock );

		CleanupEnvironment( true );

		EnumD3D9::instance().Finalize();

		::timeBeginPeriod(0);
	}

	// FindValidSettings
	bool CDevice9::FindValidSettings(
		_DeviceSettings * prefered , _DeviceSettings * output , _DeviceSettings * optimal , const _MatchOptions * options )
	{
		// Find the best combination of:
		//      Adapter Ordinal
		//      Device Type
		//      Adapter Format
		//      Back Buffer Format
		//      Windowed
		// given what's available on the system and the match options combined with the device prefered input.
		// This combination of prefered is encapsulated by the CD3D9EnumDeviceSettingsCombo class.
		float fBestRanking = -1.0f;
		EnumDeviceSettings9 * pBestDeviceSettings = NULL;
		D3DDISPLAYMODE adapterDesktopDisplayMode;

		EnumD3D9& enum9 = EnumD3D9::instance();
		const std::vector<EnumAdapterInfo9*>& adapterList = enum9.GetAdapterInfoList();

		for( size_t iAdapter = 0 ; iAdapter < adapterList.size() ; ++iAdapter )
		{
			EnumAdapterInfo9 * pAdapterInfo = adapterList[iAdapter];

			// Get the desktop display mode of adapter 
			d3d9()->GetAdapterDisplayMode( pAdapterInfo->m_adapterOrdinal , &adapterDesktopDisplayMode );

			// Enum all the device types supported by this adapter to find the best device prefered
			for( size_t iDeviceInfo = 0 ; iDeviceInfo < pAdapterInfo->m_deviceInfoList.size() ; ++iDeviceInfo )
			{
				EnumDeviceInfo9 * pDeviceInfo = pAdapterInfo->m_deviceInfoList[iDeviceInfo];

				// Enum all the device prefered combinations.  A device prefered combination is 
				// a unique set of an adapter format, back buffer format, and IsWindowed.
				for( size_t iDevice = 0 ; iDevice < pDeviceInfo->m_deviceSettingsList.size() ; ++iDevice )
				{
					EnumDeviceSettings9 * pDeviceSettings = pDeviceInfo->m_deviceSettingsList[iDevice];

					// If windowed mode the adapter format has to be the same as the desktop 
					// display mode format so skip any that don't match
					if( pDeviceSettings->m_windowed && ( pDeviceSettings->m_adapterFormat != adapterDesktopDisplayMode.Format ) )
						continue;

					// Skip any combo that doesn't meet the preserve match options
					{
						//---------------------
						// Adapter ordinal
						//---------------------
						if( options->AdapterOrdinal == MATCH_PRESERVE_INPUT &&
							( pDeviceSettings->m_adapterOrdinal != prefered->adapterOrdinal ) )
							continue;

						//---------------------
						// Device type
						//---------------------
						if( options->DeviceType == MATCH_PRESERVE_INPUT &&
							( pDeviceSettings->m_deviceType != prefered->deviceType ) )
							continue;

						//---------------------
						// Windowed
						//---------------------
						if( options->Windowed == MATCH_PRESERVE_INPUT &&
							( pDeviceSettings->m_windowed != prefered->presentParameters.Windowed ) )
							continue;

						//---------------------
						// Adapter format
						//---------------------
						if( options->AdapterFormat == MATCH_PRESERVE_INPUT &&
							( pDeviceSettings->m_adapterFormat != prefered->adapterFormat ) )
							continue;

						//---------------------
						// Vertex processing
						//---------------------
						// If keep VP and input has HWVP, then skip if this combo doesn't have HWTL 
						if( options->VertexProcessing == MATCH_PRESERVE_INPUT &&
							( ( ( prefered->behaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING ) != 0 ) &&
							 ( ( pDeviceSettings->m_deviceInfo->m_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) == 0 ) ) )
							 continue;

						//---------------------
						// Resolution
						//---------------------
						// If keep resolution then check that width and height supported by this prefered
						if( options->Resolution == MATCH_PRESERVE_INPUT )
						{
							bool bFound = false;
							for( size_t i = 0 ; i < pDeviceSettings->m_adapterInfo->m_displayModeList.size() ; ++i )
							{
								const D3DDISPLAYMODE& displayMode = pDeviceSettings->m_adapterInfo->m_displayModeList[i];
								if( displayMode.Format != pDeviceSettings->m_adapterFormat )
									continue; // Skip this display mode if it doesn't match the combo's adapter format

								if( displayMode.Width == prefered->presentParameters.BackBufferWidth &&
									displayMode.Height == prefered->presentParameters.BackBufferHeight )
								{
									bFound = true;
									break;
								}
							}

							// If the width and height are not supported by this combo, return false
							if( !bFound )
								continue;
						}

						//---------------------
						// Back buffer format
						//---------------------
						if( options->BackBufferFormat == MATCH_PRESERVE_INPUT &&
							( pDeviceSettings->m_backbufferFormat != prefered->presentParameters.BackBufferFormat ) )
							continue;

						//---------------------
						// Multisample
						//---------------------
						if( options->MultiSample == MATCH_PRESERVE_INPUT )
						{
							bool bFound = false;
							for( size_t i = 0 ; i < pDeviceSettings->m_multiSampleTypeList.size() ; ++i )
							{
								const D3DMULTISAMPLE_TYPE& msType = pDeviceSettings->m_multiSampleTypeList[i];
								DWORD msQuality = pDeviceSettings->m_multiSampleQualityList[i];

								if( msType == prefered->presentParameters.MultiSampleType &&
									msQuality > prefered->presentParameters.MultiSampleQuality )
								{
									bFound = true;
									break;
								}
							}

							// If multisample type/quality not supported by this combo, then return false
							if( !bFound )
								continue;
						}

						//---------------------
						// Depth stencil 
						//---------------------
						if( options->DepthFormat == MATCH_PRESERVE_INPUT &&
							options->StencilFormat == MATCH_PRESERVE_INPUT )
						{
							if( prefered->presentParameters.AutoDepthStencilFormat != D3DFMT_UNKNOWN )
							{
								// Find stencil format
								bool bFound = false;
								for( size_t i = 0 ; i < pDeviceSettings->m_depthStencilFormatList.size() ; ++i )
								{
									if( pDeviceSettings->m_depthStencilFormatList[i] == 
										prefered->presentParameters.AutoDepthStencilFormat )
									{
										bFound = true;
										break;
									}
								}
								if( !bFound )
									continue;
							}

							// If keep depth format then check that the depth format is supported by this prefered
							if( options->DepthFormat == MATCH_PRESERVE_INPUT &&
								( prefered->presentParameters.AutoDepthStencilFormat != D3DFMT_UNKNOWN ) )
							{
								bool bFound = false;
								UINT dwDepthBits = d3d_get_depth_bits( prefered->presentParameters.AutoDepthStencilFormat );
								for( size_t i = 0 ; i < pDeviceSettings->m_depthStencilFormatList.size(); ++i )
								{
									D3DFORMAT depthStencilFmt = pDeviceSettings->m_depthStencilFormatList[i];
									UINT dwCurDepthBits = d3d_get_depth_bits( depthStencilFmt );
									if( dwCurDepthBits - dwDepthBits == 0)
										bFound = true;
								}

								if( !bFound )
									continue;
							}

							// If keep depth format then check that the depth format is supported by this prefered
							if( options->StencilFormat == MATCH_PRESERVE_INPUT &&
								( prefered->presentParameters.AutoDepthStencilFormat != D3DFMT_UNKNOWN ) )
							{
								bool bFound = false;
								UINT dwStencilBits = d3d_get_stencil_bits( prefered->presentParameters.AutoDepthStencilFormat );
								for( size_t i = 0 ; i < pDeviceSettings->m_depthStencilFormatList.size(); ++i )
								{
									D3DFORMAT depthStencilFmt = pDeviceSettings->m_depthStencilFormatList[i];
									UINT dwCurStencilBits = d3d_get_stencil_bits( depthStencilFmt );
									if( dwCurStencilBits - dwStencilBits == 0)
										bFound = true;
								}

								if( !bFound )
									continue;
							}
						} // Depth stencil

						//---------------------
						// Present flags
						//---------------------
						// No caps for the present flags

						//---------------------
						// Refresh rate
						//---------------------
						// If keep refresh rate then check that the resolution is supported by this combo
						if( options->RefreshRate == MATCH_PRESERVE_INPUT )
						{
							bool bFound = false;
							for( size_t i = 0 ; i < pDeviceSettings->m_adapterInfo->m_displayModeList.size() ; ++i )
							{
								D3DDISPLAYMODE displayMode = pDeviceSettings->m_adapterInfo->m_displayModeList[i];
								if( displayMode.Format != pDeviceSettings->m_adapterFormat )
									continue;
								if( displayMode.RefreshRate == prefered->presentParameters.FullScreen_RefreshRateInHz )
								{
									bFound = true;
									break;
								}
							}

							// If refresh rate not supported by this combo, then return false
							if( !bFound )
								continue;
						}

						//---------------------
						// Present interval
						//---------------------
						// If keep present interval then check that the present interval is supported by this combo
						if( options->PresentInterval == MATCH_PRESERVE_INPUT )
						{
							bool bFound = false;
							for( size_t i = 0 ; i < pDeviceSettings->m_presentIntervalList.size(); ++i )
							{
								if( pDeviceSettings->m_presentIntervalList[i] ==
									prefered->presentParameters.PresentationInterval )
								{
									bFound = true;
									break;
								}
							}
							if( !bFound )
								continue;
						}

					} // Skip any combo that doesn't meet the preserve match options

					// Get a ranking number that describes how closely this device combo matches the optimal combo
					{
						float fCurRanking = -1;

						// Arbitrary weights.  Gives preference to the ordinal, device type, and windowed
						const float fAdapterOrdinalWeight   = 1000.0f;
						const float fDeviceTypeWeight       = 100.0f;
						const float fWindowWeight           = 10.0f;
						const float fAdapterFormatWeight    = 1.0f;
						const float fVertexProcessingWeight = 1.0f;
						const float fResolutionWeight       = 1.0f;
						const float fBackBufferFormatWeight = 1.0f;
						const float fMultiSampleWeight      = 1.0f;
						const float fDepthStencilWeight     = 1.0f;
						const float fRefreshRateWeight      = 1.0f;
						const float fPresentIntervalWeight  = 1.0f;

						//---------------------
						// Adapter ordinal
						//---------------------
						if( pDeviceSettings->m_adapterOrdinal == optimal->adapterOrdinal )
							fCurRanking += fAdapterOrdinalWeight;

						//---------------------
						// Device type
						//---------------------
						if( pDeviceSettings->m_deviceType == optimal->deviceType )
							fCurRanking += fDeviceTypeWeight;
						// Slightly prefer HAL 
						if( pDeviceSettings->m_deviceType == D3DDEVTYPE_HAL )
							fCurRanking += 0.1f; 

						//---------------------
						// Windowed
						//---------------------
						if( pDeviceSettings->m_windowed == optimal->presentParameters.Windowed )
							fCurRanking += fWindowWeight;

						//---------------------
						// Adapter format
						//---------------------
						if( pDeviceSettings->m_adapterFormat == optimal->adapterFormat )
						{
							fCurRanking += fAdapterFormatWeight;
						}
						else
						{
							int nBitDepthDelta = abs( (long) d3d_get_color_bits(pDeviceSettings->m_adapterFormat) -
								(long) d3d_get_color_bits(optimal->adapterFormat) );
							float fScale = __max(0.9f - (float)nBitDepthDelta*0.2f, 0.0f);
							fCurRanking += fScale * fAdapterFormatWeight;
						}

						if( !pDeviceSettings->m_windowed )
						{
							// Slightly prefer when it matches the desktop format or is D3DFMT_X8R8G8B8
							bool bAdapterOptimalMatch;
							if( d3d_get_color_bits( adapterDesktopDisplayMode.Format ) >= 8 )
								bAdapterOptimalMatch = (pDeviceSettings->m_adapterFormat == adapterDesktopDisplayMode.Format);
							else
								bAdapterOptimalMatch = (pDeviceSettings->m_adapterFormat == D3DFMT_X8R8G8B8);

							if( bAdapterOptimalMatch )
								fCurRanking += 0.1f;
						}

						//---------------------
						// Vertex processing
						//---------------------
						if( (optimal->behaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING) != 0 || 
							(optimal->behaviorFlags & D3DCREATE_MIXED_VERTEXPROCESSING) != 0 )
						{
							if( (pDeviceSettings->m_deviceInfo->m_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0 )
								fCurRanking += fVertexProcessingWeight;
						}
						// Slightly prefer HW T&L
						if( (pDeviceSettings->m_deviceInfo->m_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0 )
							fCurRanking += 0.1f;

						//---------------------
						// Resolution
						//---------------------
						bool bResolutionFound = false;
						for( size_t idm = 0 ; idm < pDeviceSettings->m_adapterInfo->m_displayModeList.size(); ++idm )
						{
							D3DDISPLAYMODE displayMode = pDeviceSettings->m_adapterInfo->m_displayModeList[idm];
							if( displayMode.Format != pDeviceSettings->m_adapterFormat )
								continue;
							if( displayMode.Width == optimal->presentParameters.BackBufferWidth &&
								displayMode.Height == optimal->presentParameters.BackBufferHeight )
								bResolutionFound = true;
						}
						if( bResolutionFound )
							fCurRanking += fResolutionWeight;

						//---------------------
						// Back buffer format
						//---------------------
						if( pDeviceSettings->m_backbufferFormat == optimal->presentParameters.BackBufferFormat )
						{
							fCurRanking += fBackBufferFormatWeight;
						}
						else
						{
							int nBitDepthDelta = abs(
								(long) d3d_get_color_bits( pDeviceSettings->m_backbufferFormat ) -
								(long) d3d_get_color_bits( optimal->presentParameters.BackBufferFormat ) );
							float fScale = __max( 0.9f - (float)nBitDepthDelta * 0.2f , 0.0f );
							fCurRanking += fScale * fBackBufferFormatWeight;
						}

						// Check if this back buffer format is the same as 
						// the adapter format since this is preferred.
						bool bAdapterMatchesBB = (pDeviceSettings->m_backbufferFormat == pDeviceSettings->m_adapterFormat);
						if( bAdapterMatchesBB )
							fCurRanking += 0.1f;

						//---------------------
						// Back buffer count
						//---------------------
						// No caps for the back buffer count

						//---------------------
						// Multisample
						//---------------------
						bool bMultiSampleFound = false;
						for( size_t i = 0 ; i < pDeviceSettings->m_multiSampleTypeList.size(); ++i )
						{
							D3DMULTISAMPLE_TYPE msType = pDeviceSettings->m_multiSampleTypeList[i];
							DWORD msQuality = pDeviceSettings->m_multiSampleQualityList[i];

							if( msType == optimal->presentParameters.MultiSampleType &&
								msQuality > optimal->presentParameters.MultiSampleQuality )
							{
								bMultiSampleFound = true;
								break;
							}
						}
						if( bMultiSampleFound )
							fCurRanking += fMultiSampleWeight;

						//---------------------
						// Swap effect
						//---------------------
						// No caps for swap effects

						//---------------------
						// Depth stencil 
						//---------------------
						{
							for( int i = 0 ; i < (int)pDeviceSettings->m_depthStencilFormatList.size() ; ++i )
							{
								if( optimal->presentParameters.AutoDepthStencilFormat == 
									pDeviceSettings->m_depthStencilFormatList[i] )
								{
									fCurRanking += fDepthStencilWeight;
									break;
								}
							}
						} // Depth stencil		

						//---------------------
						// Present flags
						//---------------------
						// No caps for the present flags

						//---------------------
						// Refresh rate
						//---------------------
						bool bRefreshFound = false;
						for( int idm = 0 ; idm < (int)pDeviceSettings->m_adapterInfo->m_displayModeList.size() ; ++idm )
						{
							D3DDISPLAYMODE displayMode = pDeviceSettings->m_adapterInfo->m_displayModeList[idm];
							if( displayMode.Format != pDeviceSettings->m_adapterFormat )
								continue;
							if( displayMode.RefreshRate == optimal->presentParameters.FullScreen_RefreshRateInHz )
								bRefreshFound = true;
						}
						if( bRefreshFound )
							fCurRanking += fRefreshRateWeight;

						//---------------------
						// Present interval
						//---------------------
						// If keep present interval then check that the present interval is supported by this combo
						for( int i = 0 ; i < (int)pDeviceSettings->m_presentIntervalList.size() ; ++i )
						{
							if( pDeviceSettings->m_presentIntervalList[i] == 
								optimal->presentParameters.PresentationInterval )
							{
								fCurRanking += fPresentIntervalWeight;
								break;
							}
						}

						// If this combo better matches the input device prefered then save it
						if( fCurRanking > fBestRanking )
						{
							pBestDeviceSettings = pDeviceSettings;
							fBestRanking = fCurRanking;
						}
					} // Get a ranking number that describes how closely this device combo matches the optimal combo
				} // for every settings
			} // for every device
		} // for every adapter

		if( pBestDeviceSettings == NULL )
			return false;

		// Using the best device settings combo found, build valid device settings taking heed of 
		// the match options and the input device settings
		{
			d3d9()->GetAdapterDisplayMode( pBestDeviceSettings->m_adapterOrdinal, &adapterDesktopDisplayMode );

			// For each setting pick the best, taking into account the match options and 
			// what's supported by the device

			//---------------------
			// Adapter Ordinal
			//---------------------
			// Just using pBestDeviceSettings->AdapterOrdinal

			//---------------------
			// Device Type
			//---------------------
			// Just using pBestDeviceSettings->DeviceType

			//---------------------
			// Windowed 
			//---------------------
			// Just using pBestDeviceSettings->Windowed

			//---------------------
			// Adapter Format
			//---------------------
			// Just using pBestDeviceSettings->AdapterFormat

			//---------------------
			// Vertex processing
			//---------------------
			DWORD dwBestBehaviorFlags = 0; 
			if( options->VertexProcessing == MATCH_PRESERVE_INPUT )   
			{
				dwBestBehaviorFlags = prefered->behaviorFlags;
			}
			else if( options->VertexProcessing == MATCH_IGNORE_INPUT )    
			{
				// The framework defaults to HWVP if available otherwise use SWVP
				if( ( pBestDeviceSettings->m_deviceInfo->m_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) != 0 )
					dwBestBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
				else
					dwBestBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
			}
			else // if( options->VertexProcessing == MATCH_CLOSEST_TO_INPUT )    
			{
				// Default to input, and fallback to SWVP if HWVP not available 
				dwBestBehaviorFlags = prefered->behaviorFlags;
				if ((pBestDeviceSettings->m_deviceInfo->m_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) == 0 && 
					( (dwBestBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING) != 0 || 
					(dwBestBehaviorFlags & D3DCREATE_MIXED_VERTEXPROCESSING) != 0) )
				{
					dwBestBehaviorFlags &= ~D3DCREATE_HARDWARE_VERTEXPROCESSING;
					dwBestBehaviorFlags &= ~D3DCREATE_MIXED_VERTEXPROCESSING;
					dwBestBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
				}

				// One of these must be selected
				if( (dwBestBehaviorFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING) == 0 &&
					(dwBestBehaviorFlags & D3DCREATE_MIXED_VERTEXPROCESSING) == 0 &&
					(dwBestBehaviorFlags & D3DCREATE_SOFTWARE_VERTEXPROCESSING) == 0 )
				{
					if ((pBestDeviceSettings->m_deviceInfo->m_caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
						dwBestBehaviorFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
					else
						dwBestBehaviorFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;
				}
			}

			//---------------------
			// Resolution
			//---------------------
			D3DDISPLAYMODE bestDisplayMode;  
			if( options->Resolution == MATCH_PRESERVE_INPUT )   
			{
				bestDisplayMode.Width = prefered->presentParameters.BackBufferWidth;
				bestDisplayMode.Height = prefered->presentParameters.BackBufferHeight;
			}
			else 
			{
				D3DDISPLAYMODE displayModeIn;  
				if( options->Resolution == MATCH_CLOSEST_TO_INPUT &&
					prefered )   
				{
					displayModeIn.Width = prefered->presentParameters.BackBufferWidth;
					displayModeIn.Height = prefered->presentParameters.BackBufferHeight;
				}
				else // if( options->Resolution == MATCH_IGNORE_INPUT )   
				{
					if( pBestDeviceSettings->m_windowed )
					{
						// The framework defaults to 640x480 for windowed
						displayModeIn.Width = 640;
						displayModeIn.Height = 480;
					}
					else
					{
						// The framework defaults to desktop resolution for fullscreen to try to avoid slow mode change
						displayModeIn.Width = adapterDesktopDisplayMode.Width;
						displayModeIn.Height = adapterDesktopDisplayMode.Height;
					}
				}

				// Find the closest valid display mode to the optimal.
				{
					D3DDISPLAYMODE resBestMode;
					ZeroMemory( &resBestMode, sizeof(D3DDISPLAYMODE) );

					if( pBestDeviceSettings->m_windowed )
					{
						// In windowed mode, all resolutions are valid but restrictions still apply 
						// on the size of the window.  See DXUTChangeD3D9Device() for details
						bestDisplayMode = displayModeIn;
					}
					else
					{
						int nBestRanking = 100000;
						int nCurRanking;
						std::vector<D3DDISPLAYMODE>& displayModeList = pBestDeviceSettings->m_adapterInfo->m_displayModeList;
						for( size_t iDisplayMode = 0 ; iDisplayMode < displayModeList.size() ; ++iDisplayMode )
						{
							D3DDISPLAYMODE displayMode = displayModeList[iDisplayMode];

							// Skip display modes that don't match the combo's adapter format
							if( displayMode.Format != pBestDeviceSettings->m_adapterFormat )
								continue;

							// Find the delta between the current width/height and the optimal width/height
							nCurRanking = abs((int)displayMode.Width - (int)displayModeIn.Width) + 
								abs((int)displayMode.Height- (int)displayModeIn.Height);

							if( nCurRanking < nBestRanking )
							{
								resBestMode = displayMode;
								nBestRanking = nCurRanking;

								// Stop if perfect match found
								if( nBestRanking == 0 )
									break;
							}
						}

						if( resBestMode.Width == 0 )
						{
							// No valid display modes found
							bestDisplayMode = displayModeIn;
						}
						else
						{
							bestDisplayMode = resBestMode;
						}
					}
				} // find valid resolution
			}

			//---------------------
			// Back Buffer Format
			//---------------------
			// Just using pBestDeviceSettings->BackBufferFormat

			//---------------------
			// Back buffer count
			//---------------------
			UINT bestBackBufferCount;
			if( options->BackBufferCount == MATCH_PRESERVE_INPUT )   
			{
				bestBackBufferCount = prefered->presentParameters.BackBufferCount;
			}
			else if( options->BackBufferCount == MATCH_IGNORE_INPUT )   
			{
				// Default to double buffering.  Causes less latency than triple buffering
				bestBackBufferCount = 1;
			}
			else // if( options->BackBufferCount == MATCH_CLOSEST_TO_INPUT )   
			{
				bestBackBufferCount = prefered->presentParameters.BackBufferCount;
				if( bestBackBufferCount > 3 )
					bestBackBufferCount = 3;
				if( bestBackBufferCount < 1 )
					bestBackBufferCount = 1;
			}

			//---------------------
			// Multisample
			//---------------------
			D3DMULTISAMPLE_TYPE bestMultiSampleType;
			DWORD bestMultiSampleQuality;
			if( prefered && prefered->presentParameters.SwapEffect != D3DSWAPEFFECT_DISCARD )
			{
				// Swap effect is not set to discard so multisampling has to off
				bestMultiSampleType = D3DMULTISAMPLE_NONE;
				bestMultiSampleQuality = 0;
			}
			else
			{
				if( options->MultiSample == MATCH_PRESERVE_INPUT )
				{
					bestMultiSampleType    = prefered->presentParameters.MultiSampleType;
					bestMultiSampleQuality = prefered->presentParameters.MultiSampleQuality;
				}
				else if( options->MultiSample == MATCH_IGNORE_INPUT )   
				{
					// Default to no multisampling (always supported)
					bestMultiSampleType = D3DMULTISAMPLE_NONE;
					bestMultiSampleQuality = 0;
				}
 				else if( options->MultiSample == MATCH_CLOSEST_TO_INPUT )   
				{
					// Default to no multisampling (always supported)
					bestMultiSampleType = D3DMULTISAMPLE_NONE;
					bestMultiSampleQuality = 0;

					for( int i = 0; i < (int)pBestDeviceSettings->m_multiSampleTypeList.size(); ++i )
					{
						D3DMULTISAMPLE_TYPE type = pBestDeviceSettings->m_multiSampleTypeList[i];
						DWORD qualityLevels = pBestDeviceSettings->m_multiSampleQualityList[i];

						// Check whether supported type is closer to the input than our current best
						if( abs(type - prefered->presentParameters.MultiSampleType) <
							abs(bestMultiSampleType - prefered->presentParameters.MultiSampleType) )
						{
							bestMultiSampleType = type; 
							bestMultiSampleQuality = __min( qualityLevels - 1, prefered->presentParameters.MultiSampleQuality );
						}
					}
				}
				else
				{
					// Error case
					bestMultiSampleType = D3DMULTISAMPLE_NONE;
					bestMultiSampleQuality = 0;
				}
			}

			//---------------------
			// Swap effect
			//---------------------
			D3DSWAPEFFECT bestSwapEffect;
			if( options->SwapEffect == MATCH_PRESERVE_INPUT )   
			{
				bestSwapEffect = prefered->presentParameters.SwapEffect;
			}
			else if( options->SwapEffect == MATCH_IGNORE_INPUT )   
			{
				bestSwapEffect = D3DSWAPEFFECT_DISCARD;
			}
			else // if( options->SwapEffect == MATCH_CLOSEST_TO_INPUT )   
			{
				bestSwapEffect = prefered->presentParameters.SwapEffect;

				// Swap effect has to be one of these 3
				if( bestSwapEffect != D3DSWAPEFFECT_DISCARD &&
					bestSwapEffect != D3DSWAPEFFECT_FLIP &&
					bestSwapEffect != D3DSWAPEFFECT_COPY )
				{
					bestSwapEffect = D3DSWAPEFFECT_DISCARD;
				}
			}

			//---------------------
			// Depth stencil 
			//---------------------
			D3DFORMAT bestDepthStencilFormat;
			bool bestEnableAutoDepthStencil;

			std::vector<int> depthStencilRanking;

			UINT dwBackBufferBitDepth = d3d_get_color_bits( pBestDeviceSettings->m_backbufferFormat );
			UINT dwInputDepthBitDepth = 0;
			if( prefered )
				dwInputDepthBitDepth = d3d_get_depth_bits( prefered->presentParameters.AutoDepthStencilFormat );

			for( size_t i = 0 ; i < pBestDeviceSettings->m_depthStencilFormatList.size() ; ++i )
			{
				D3DFORMAT curDepthStencilFmt = pBestDeviceSettings->m_depthStencilFormatList[i];
				DWORD dwCurDepthBitDepth = d3d_get_depth_bits( curDepthStencilFmt );
				int nRanking;

				if( options->DepthFormat == MATCH_PRESERVE_INPUT )
				{                       
					// Need to match bit depth of input
					if( dwCurDepthBitDepth == dwInputDepthBitDepth )
						nRanking = 0;
					else
						nRanking = 10000;
				}
				else if( options->DepthFormat == MATCH_IGNORE_INPUT )
				{
					// Prefer match of backbuffer bit depth
					nRanking = abs( (int)dwCurDepthBitDepth - (int)dwBackBufferBitDepth*4 );
				}
				else // if( options->DepthFormat == MATCH_CLOSEST_TO_INPUT )
				{
					// Prefer match of input depth format bit depth
					nRanking = abs((int)dwCurDepthBitDepth - (int)dwInputDepthBitDepth);
				}

				depthStencilRanking.push_back( nRanking );
			}

			UINT dwInputStencilBitDepth = 0;
			if( prefered )
				dwInputStencilBitDepth = d3d_get_stencil_bits( prefered->presentParameters.AutoDepthStencilFormat );

			for( int i=0; i < (int)pBestDeviceSettings->m_depthStencilFormatList.size(); ++i )
			{
				D3DFORMAT curDepthStencilFmt = pBestDeviceSettings->m_depthStencilFormatList[i];
				int nRanking = depthStencilRanking[i];
				DWORD dwCurStencilBitDepth = d3d_get_stencil_bits( curDepthStencilFmt );

				if( options->StencilFormat == MATCH_PRESERVE_INPUT )
				{                       
					// Need to match bit depth of input
					if(dwCurStencilBitDepth == dwInputStencilBitDepth)
						nRanking += 0;
					else
						nRanking += 10000;
				}
				else if( options->StencilFormat == MATCH_IGNORE_INPUT )
				{
					// Prefer 0 stencil bit depth
					nRanking += dwCurStencilBitDepth;
				}
				else // if( options->StencilFormat == MATCH_CLOSEST_TO_INPUT )
				{
					// Prefer match of input stencil format bit depth
					nRanking += abs((int)dwCurStencilBitDepth - (int)dwInputStencilBitDepth);
				}

				depthStencilRanking[i] = nRanking;
			}

			int nBestRanking = 100000;
			int nBestIndex = -1;
			for( int i=0; i < (int)pBestDeviceSettings->m_depthStencilFormatList.size(); ++i )
			{
				int nRanking = depthStencilRanking[i];
				if( nRanking < nBestRanking )
				{
					nBestRanking = nRanking;
					nBestIndex = i;
				}
			}

			if( nBestIndex >= 0 )
			{
				bestDepthStencilFormat = pBestDeviceSettings->m_depthStencilFormatList[nBestIndex];
				bestEnableAutoDepthStencil = true;
			}
			else
			{
				bestDepthStencilFormat = D3DFMT_UNKNOWN;
				bestEnableAutoDepthStencil = false;
			}


			//---------------------
			// Present flags
			//---------------------
			DWORD dwBestFlags;
			if( options->PresentFlags == MATCH_PRESERVE_INPUT )
			{
				dwBestFlags = prefered->presentParameters.Flags;
			}
			else if( options->PresentFlags == MATCH_IGNORE_INPUT )
			{
				dwBestFlags = 0;
				if( bestEnableAutoDepthStencil )
					dwBestFlags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
			}
			else // if( options->PresentFlags == MATCH_CLOSEST_TO_INPUT )   
			{
				dwBestFlags = prefered->presentParameters.Flags;
				if( bestEnableAutoDepthStencil )
					dwBestFlags |= D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
			}

			//---------------------
			// Refresh rate
			//---------------------
			if( pBestDeviceSettings->m_windowed )
			{
				// Must be 0 for windowed
				bestDisplayMode.RefreshRate = 0;
			}
			else
			{
				if( options->RefreshRate == MATCH_PRESERVE_INPUT )   
				{
					bestDisplayMode.RefreshRate = prefered->presentParameters.FullScreen_RefreshRateInHz;
				}
				else 
				{
					UINT refreshRateMatch;
					if( options->RefreshRate == MATCH_CLOSEST_TO_INPUT )   
					{
						refreshRateMatch = prefered->presentParameters.FullScreen_RefreshRateInHz;
					}
					else // if( options->RefreshRate == MATCH_IGNORE_INPUT )   
					{
						refreshRateMatch = adapterDesktopDisplayMode.RefreshRate;
					}

					bestDisplayMode.RefreshRate = 0;

					if( refreshRateMatch != 0 )
					{
						int nBestRefreshRanking = 100000;
						std::vector<D3DDISPLAYMODE>& displayModeList = pBestDeviceSettings->m_adapterInfo->m_displayModeList;
						for( int iDisplayMode = 0 ; iDisplayMode < (int)displayModeList.size(); ++iDisplayMode )
						{
							D3DDISPLAYMODE displayMode = displayModeList[iDisplayMode];                
							if( displayMode.Format != pBestDeviceSettings->m_adapterFormat || 
								displayMode.Height != bestDisplayMode.Height ||
								displayMode.Width != bestDisplayMode.Width )
								continue; // Skip display modes that don't match 

							// Find the delta between the current refresh rate and the optimal refresh rate 
							int nCurRanking = abs((int)displayMode.RefreshRate - (int)refreshRateMatch);

							if( nCurRanking < nBestRefreshRanking )
							{
								bestDisplayMode.RefreshRate = displayMode.RefreshRate;
								nBestRefreshRanking = nCurRanking;

								// Stop if perfect match found
								if( nBestRefreshRanking == 0 )
									break;
							}
						}
					}
				}
			}

			//---------------------
			// Present interval
			//---------------------
			UINT bestPresentInterval;
			if( options->PresentInterval == MATCH_PRESERVE_INPUT )
			{
				bestPresentInterval = prefered->presentParameters.PresentationInterval;
			}
			else if( options->PresentInterval == MATCH_IGNORE_INPUT )
			{
				// For windowed and fullscreen, default to D3DPRESENT_INTERVAL_DEFAULT
				// which will wait for the vertical retrace period to prevent tearing.
				// For benchmarking, use D3DPRESENT_INTERVAL_DEFAULT  which will
				// will wait not for the vertical retrace period but may introduce tearing.
				bestPresentInterval = D3DPRESENT_INTERVAL_DEFAULT;
			}
			else // if( options->PresentInterval == MATCH_CLOSEST_TO_INPUT )
			{
				bool bFound = false;
				for( size_t i = 0 ; i < pBestDeviceSettings->m_presentIntervalList.size() ; ++i )
				{
					if( pBestDeviceSettings->m_presentIntervalList[i] == prefered->presentParameters.PresentationInterval )
					{
						bFound = true;
						break;
					}
				}

				if( bFound )
				{
					bestPresentInterval = prefered->presentParameters.PresentationInterval;
				}
				else
				{
					bestPresentInterval = D3DPRESENT_INTERVAL_DEFAULT;
				}
			}

			// Fill the device settings struct
			ZeroMemory( output, sizeof(_DeviceSettings) );
			output->adapterOrdinal                				  	= pBestDeviceSettings->m_adapterOrdinal;
			output->deviceType                    				  	= pBestDeviceSettings->m_deviceType;
			output->adapterFormat                 				  	= pBestDeviceSettings->m_adapterFormat;
			output->behaviorFlags                 				  	= dwBestBehaviorFlags;
			output->presentParameters.BackBufferWidth             	= bestDisplayMode.Width;
			output->presentParameters.BackBufferHeight            	= bestDisplayMode.Height;
			output->presentParameters.BackBufferFormat				= pBestDeviceSettings->m_backbufferFormat;
			output->presentParameters.BackBufferCount             	= bestBackBufferCount;
			output->presentParameters.MultiSampleType             	= bestMultiSampleType;  
			output->presentParameters.MultiSampleQuality          	= bestMultiSampleQuality;
			output->presentParameters.SwapEffect                  	= bestSwapEffect;
			output->presentParameters.hDeviceWindow               	= prefered->presentParameters.hDeviceWindow;
			output->presentParameters.Windowed                    	= pBestDeviceSettings->m_windowed;
			output->presentParameters.EnableAutoDepthStencil      	= bestEnableAutoDepthStencil;  
			output->presentParameters.AutoDepthStencilFormat      	= bestDepthStencilFormat;
			output->presentParameters.Flags                       	= dwBestFlags;                   
			output->presentParameters.FullScreen_RefreshRateInHz  	= bestDisplayMode.RefreshRate;
			output->presentParameters.PresentationInterval        	= bestPresentInterval;
		}
		return true;
	}

	// BuildOptimalSettings
	void CDevice9::BuildOptimalSettings( _DeviceSettings * prefered , _DeviceSettings * optimal ,
		const _MatchOptions * options )
	{
		D3DDISPLAYMODE adapterDesktopDisplayMode;
		ZeroMemory( optimal, sizeof(_DeviceSettings) ); 

		//---------------------
		// Adapter ordinal
		//---------------------    
		if( options->AdapterOrdinal == MATCH_IGNORE_INPUT )
			optimal->adapterOrdinal = D3DADAPTER_DEFAULT; 
		else
			optimal->adapterOrdinal = prefered->adapterOrdinal;      

		//---------------------
		// Device type
		//---------------------
		if( options->DeviceType == MATCH_IGNORE_INPUT )
			optimal->deviceType = D3DDEVTYPE_HAL; 
		else
			optimal->deviceType = prefered->deviceType;

		//---------------------
		// Windowed
		//---------------------
		if( options->Windowed == MATCH_IGNORE_INPUT )
			optimal->presentParameters.Windowed = TRUE; 
		else
			optimal->presentParameters.Windowed = prefered->presentParameters.Windowed;

		//---------------------
		// Adapter format
		//---------------------
		if( options->AdapterFormat == MATCH_IGNORE_INPUT )
		{
			// If windowed, default to the desktop display mode
			// If fullscreen, default to the desktop display mode for quick mode change or 
			// default to D3DFMT_X8R8G8B8 if the desktop display mode is < 32bit
			d3d9()->GetAdapterDisplayMode( optimal->adapterOrdinal, &adapterDesktopDisplayMode );
			if( optimal->presentParameters.Windowed || d3d_get_color_bits(adapterDesktopDisplayMode.Format) >= 8 )
				optimal->adapterFormat = adapterDesktopDisplayMode.Format;
			else
				optimal->adapterFormat = D3DFMT_X8R8G8B8;
		}
		else
		{
			optimal->adapterFormat = prefered->adapterFormat;
		}

		//---------------------
		// Vertex processing
		//---------------------
		if( options->VertexProcessing == MATCH_IGNORE_INPUT )
			optimal->behaviorFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING; 
		else
			optimal->behaviorFlags = prefered->behaviorFlags;

		//---------------------
		// Resolution
		//---------------------
		if( options->Resolution == MATCH_IGNORE_INPUT )
		{
			// If windowed, default to 640x480
			// If fullscreen, default to the desktop res for quick mode change
			if( optimal->presentParameters.Windowed )
			{
				optimal->presentParameters.BackBufferWidth = 800;
				optimal->presentParameters.BackBufferHeight = 600;
			}
			else
			{
				d3d9()->GetAdapterDisplayMode( optimal->adapterOrdinal, &adapterDesktopDisplayMode );
				optimal->presentParameters.BackBufferWidth = adapterDesktopDisplayMode.Width;
				optimal->presentParameters.BackBufferHeight = adapterDesktopDisplayMode.Height;
			}
		}
		else
 		{
			optimal->presentParameters.BackBufferWidth = prefered->presentParameters.BackBufferWidth;
			optimal->presentParameters.BackBufferHeight = prefered->presentParameters.BackBufferHeight;
		}

		//---------------------
		// Back buffer format
		//---------------------
		if( options->BackBufferFormat == MATCH_IGNORE_INPUT )
			optimal->presentParameters.BackBufferFormat = optimal->adapterFormat; // Default to match the adapter format
		else
			optimal->presentParameters.BackBufferFormat = prefered->presentParameters.BackBufferFormat;

		//---------------------
		// Back buffer count
		//---------------------
		if( options->BackBufferCount == MATCH_IGNORE_INPUT )
			optimal->presentParameters.BackBufferCount = 1; // Default to double buffering.  Causes less latency than triple buffering
		else
			optimal->presentParameters.BackBufferCount = prefered->presentParameters.BackBufferCount;

		//---------------------
		// Multisample
		//---------------------
		if( options->MultiSample == MATCH_IGNORE_INPUT )
 		{
			// Default to no multisampling 
			optimal->presentParameters.MultiSampleType = D3DMULTISAMPLE_NONE;
			optimal->presentParameters.MultiSampleQuality = 0; 
		}
		else
		{
			optimal->presentParameters.MultiSampleType = prefered->presentParameters.MultiSampleType;
			optimal->presentParameters.MultiSampleQuality = prefered->presentParameters.MultiSampleQuality;
		}

		//---------------------
		// Swap effect
		//---------------------
		if( options->SwapEffect == MATCH_IGNORE_INPUT )
			optimal->presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD; 
		else
			optimal->presentParameters.SwapEffect = prefered->presentParameters.SwapEffect;

		//---------------------
		// Depth stencil 
		//---------------------
		if( options->DepthFormat == MATCH_IGNORE_INPUT &&
			options->StencilFormat == MATCH_IGNORE_INPUT )
		{
			UINT nBackBufferBits = d3d_get_color_bits( optimal->presentParameters.BackBufferFormat );
			if( nBackBufferBits >= 8 )
				optimal->presentParameters.AutoDepthStencilFormat = D3DFMT_D32; 
			else
				optimal->presentParameters.AutoDepthStencilFormat = D3DFMT_D16; 
		}
		else
		{
			optimal->presentParameters.AutoDepthStencilFormat = prefered->presentParameters.AutoDepthStencilFormat;
		}

		//---------------------
		// Present flags
		//---------------------
		if( options->PresentFlags == MATCH_IGNORE_INPUT )
			optimal->presentParameters.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
		else
			optimal->presentParameters.Flags = prefered->presentParameters.Flags;

		//---------------------
		// Refresh rate
		//---------------------
		if( options->RefreshRate == MATCH_IGNORE_INPUT )
			optimal->presentParameters.FullScreen_RefreshRateInHz = 0;
		else
			optimal->presentParameters.FullScreen_RefreshRateInHz = prefered->presentParameters.FullScreen_RefreshRateInHz;

		//---------------------
		// Present interval
		//---------------------
		if( options->PresentInterval == MATCH_IGNORE_INPUT )
		{
			// For windowed and fullscreen, default to D3DPRESENT_INTERVAL_DEFAULT
			// which will wait for the vertical retrace period to prevent tearing.
			// For benchmarking, use D3DPRESENT_INTERVAL_IMMEDIATE which will
			// will wait not for the vertical retrace period but may introduce tearing.
			optimal->presentParameters.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
		}
		else
		{
			optimal->presentParameters.PresentationInterval = prefered->presentParameters.PresentationInterval;
		}
	}
	// GetBackBufferSize
	Pair<int> CDevice9::GetBackBufferSize( void )
	{
		return Pair<int>( m_backBufferSurfaceDesc.Width , m_backBufferSurfaceDesc.Height );
	}
} // namespace xeres
